﻿using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
using ST.Buz.Base.Entity;
using ST.Common.Util;
using ST.Common.Util.Mvc;
using ST.Common.Util.Security;
using ST.WX.Common;
using ST.WX.Common.Enums;
using ST.WX.Common.Model;
using ST.WX.MP.Model;
using System;
using System.Collections.Generic;
using System.Text;

namespace ST.WX.MP
{
	/// <summary>
	/// 公众号基础相关接口
	/// </summary>
	public class WXMPBaseApi
	{
		private readonly ILogger logger;
		private readonly IMemoryCache cache;
		private readonly string TOKEN_CACHE_KEY_PREFIX = "OAUTH_";
		private readonly string TICKET_CACHE_KEY_PREFIX = "TICKET_";

		public WXBaseApi BaseApi { get; }

		public WXMPBaseApi(ILogger<WXMPBaseApi> _logger, IMemoryCache _cache, WXBaseApi _baseApi)
		{
			cache = _cache;
			logger = _logger;
			BaseApi = _baseApi;
		}

		/// <summary>
		/// 刷新网页授权凭证ACCESS_TOKEN（由于access_token拥有较短的有效期，当access_token超时后，可以使用refresh_token进行刷新，refresh_token有效期为30天，当refresh_token失效之后，需要用户重新授权）
		/// </summary>
		public WXOAuthResult RefreshOAuthAccessToken(string appID, string refreshToken)
		{
			try
			{
				if (string.IsNullOrEmpty(appID))
				{
					throw new WXException("appID不能为空");
				}

				string respText = HttpHelper.Get("https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=" + appID + "&grant_type=refresh_token&refresh_token=" + refreshToken);
				if (string.IsNullOrEmpty(respText))
				{
					throw new WXException("刷新网页授权凭证返回为空");
				}
				var ret = Newtonsoft.Json.JsonConvert.DeserializeObject<WXOAuthResult>(respText);
				if (ret.errcode != 0 || string.IsNullOrWhiteSpace(ret.access_token))
				{
					throw new WXException($"刷新网页授权凭证返回错误码：{ret.errcode}，错误消息：{ret.errmsg}");
				}
				return ret;
			}
			catch (WXException ex)
			{
				logger.LogError(ex.InnerException ?? ex, ex.Message);
			}
			catch (Exception ex)
			{
				logger.LogError(ex.InnerException ?? ex, ex.Message);
			}
			return null;
		}

		/// <summary>
		/// 获取网页授权凭证ACCESS_TOKEN（如果未过期从缓存读取，否则重新获取）
		/// </summary>
		/// <param name="appConfig"></param>
		/// <param name="code">code作为换取access_token的票据，每次用户授权带上的code将不一样，code只能使用一次，5分钟未被使用自动过期</param>
		/// <param name="openID">用户的openid，如果为空则重新获取网页授权ACCESS_TOKEN</param>
		/// <returns></returns>
		public WXOAuthResult GetOAuthAccessToken(string appID, string appSecret, string code, string openID)
		{
			try
			{
				if (string.IsNullOrEmpty(appID))
				{
					throw new WXException("appid不能为空");
				}

				string cacheKey = string.Empty;
				if (!string.IsNullOrEmpty(openID))
				{
					cacheKey = TOKEN_CACHE_KEY_PREFIX + openID;//todo:不同应用的openid不同，所以不用加appid
					WXOAuthResult retCache = cache.Get(cacheKey) as WXOAuthResult;
					if (retCache != null)
					{
						return retCache;
					}
				}
				string respText = HttpHelper.Get("https://api.weixin.qq.com/sns/oauth2/access_token?appid=" + appID + "&secret=" + appSecret + "&code=" + code + "&grant_type=authorization_code");
				if (string.IsNullOrWhiteSpace(respText))
				{
					throw new Exception("获取网页授权凭证返回为空");
				}
				var ret = Newtonsoft.Json.JsonConvert.DeserializeObject<WXOAuthResult>(respText);
				if (ret.errcode != 0 || string.IsNullOrWhiteSpace(ret.access_token))
				{
					throw new WXException($"获取网页授权凭证返回错误码：{ret.errcode}，错误消息：{ret.errmsg}");
				}
				ret.acquire = DateTime.Now;
				if (string.IsNullOrEmpty(cacheKey))
				{
					cacheKey = TOKEN_CACHE_KEY_PREFIX + ret.openid;//todo:不同应用的openid不同，所以不用加appid
				}
				MemoryCacheEntryOptions opts = new MemoryCacheEntryOptions();
				opts.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(ret.expires_in);
				//opts.AbsoluteExpiration = DateTime.SpecifyKind(DateTime.Now.AddSeconds(ret.expires_in), DateTimeKind.Local);//微信官方7200秒过期
				cache.Set(cacheKey, ret, opts);

				return ret;
			}
			catch (WXException ex)
			{
				logger.LogError(ex.InnerException ?? ex, ex.Message);
			}
			catch (Exception ex)
			{
				logger.LogError(ex.InnerException ?? ex, ex.Message);
			}
			return null;
		}

		/// <summary>
		/// 检验网页授权凭证（access_token）是否有效
		/// </summary>
		/// <param name="oauthAccessToken">网页授权接口调用凭证</param>
		/// <param name="openID"></param>
		/// <returns></returns>
		public bool ValidateOAuthAccessToken(string oauthAccessToken, string openID)
		{
			try
			{
				string respText = HttpHelper.Get("https://api.weixin.qq.com/sns/auth?access_token=" + oauthAccessToken + "&openid=" + openID);
				if (string.IsNullOrWhiteSpace(respText))
				{
					throw new Exception("检验网页授权凭证返回为空");
				}
				var ret = Newtonsoft.Json.JsonConvert.DeserializeObject<WXResult>(respText);

				if (ret.errcode != 0)
				{
					throw new WXException($"检验网页授权凭证返回错误码：{ret.errcode}，错误消息：{ret.errmsg}");
				}

				return true;
			}
			catch (WXException ex)
			{
				logger.LogError(ex.InnerException ?? ex, ex.Message);
			}
			catch (Exception ex)
			{
				logger.LogError(ex.InnerException ?? ex, ex.Message);
			}
			return false;
		}

		/// <summary>
		/// 拉取用户信息(需scope为 snsapi_userinfo)，此接口不能获取是否关注等额外信息（仅限oauth场合使用）
		/// </summary>
		/// <param name="oauthAccessToken">网页授权凭证（access_token）与access_token</param>
		/// <param name="openID"></param>
		/// <param name="lang"></param>
		public WXOAuthUserInfo GetOAuthUserInfo(string oauthAccessToken, string openID, string lang = "zh_CN")
		{
			try
			{
				string respText = HttpHelper.Get("https://api.weixin.qq.com/sns/userinfo?access_token=" + oauthAccessToken + "&openid=" + openID + "&lang=" + lang);
				if (string.IsNullOrWhiteSpace(respText))
				{
					throw new Exception("拉取用户信息返回为空");
				}
				var ret = Newtonsoft.Json.JsonConvert.DeserializeObject<WXOAuthUserInfo>(respText);
				if (ret.errcode != 0)
				{
					throw new Exception($"拉取用户信息返回错误码：{ret.errcode}，错误消息：{ret.errmsg}");
				}

				return ret;
			}
			catch (WXException ex)
			{
				logger.LogError(ex.InnerException ?? ex, ex.Message);
			}
			catch (Exception ex)
			{
				logger.LogError(ex.InnerException ?? ex, ex.Message);
			}
			return null;
		}

		/// <summary>
		/// 获取用户基本信息
		/// </summary>
		/// <param name="accessToken">普通授权ACCESS_TOKEN，与网页授权凭证（access_token）不同</param>
		/// <returns></returns>
		public WXOAuthUserInfo GetUserInfo(string accessToken, string openID, string lang = "zh_CN")
		{
			try
			{
				string respText = HttpHelper.Get("https://api.weixin.qq.com/cgi-bin/user/info?access_token=" + accessToken + "&openid=" + openID + "&lang=" + lang);
				if (string.IsNullOrWhiteSpace(respText))
				{
					throw new Exception("拉取用户信息返回为空");
				}
				var ret = Newtonsoft.Json.JsonConvert.DeserializeObject<WXOAuthUserInfo>(respText);
				if (ret.errcode != 0)
				{
					throw new Exception($"拉取用户信息返回错误码：{ret.errcode}，错误消息：{ret.errmsg}");
				}

				return ret;
			}
			catch (WXException ex)
			{
				logger.LogError(ex.InnerException ?? ex, ex.Message);
			}
			catch (Exception ex)
			{
				logger.LogError(ex.InnerException ?? ex, ex.Message);
			}
			return null;
		}

		/// <summary>
		/// 获取js令牌
		/// </summary>
		/// <param name="accessToken">普通授权access_token</param>
		/// <returns></returns>
		public string GetJSTicket(string accessToken)
		{
			try
			{
				if (string.IsNullOrEmpty(accessToken))
				{
					throw new WXException("ACCESS_TOKEN不能为空");
				}

				string cacheKey = string.Empty;
				cacheKey = TICKET_CACHE_KEY_PREFIX + accessToken;//
				WXJSApiTicketResult retCache = cache.Get(cacheKey) as WXJSApiTicketResult;
				if (retCache != null)
				{
					return retCache.ticket;
				}
				string respText = HttpHelper.Get("https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=" + accessToken + "&type=jsapi");
				if (string.IsNullOrWhiteSpace(respText))
				{
					throw new Exception("获取jsapi_ticket返回为空");
				}
				var ret = Newtonsoft.Json.JsonConvert.DeserializeObject<WXJSApiTicketResult>(respText);
				if (ret.errcode != 0 || string.IsNullOrWhiteSpace(ret.ticket))
				{
					throw new Exception($"获取jsapi_ticket返回错误码：{ret.errcode}，错误消息：{ret.errmsg}");
				}
				ret.acquire = DateTime.Now;
				MemoryCacheEntryOptions opts = new MemoryCacheEntryOptions();
				opts.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(ret.expires_in);
				//opts.AbsoluteExpiration = DateTime.SpecifyKind(DateTime.Now.AddSeconds(ret.expires_in), DateTimeKind.Local);//微信官方7200秒过期
				cache.Set(cacheKey, ret, opts);

				return ret.ticket;
			}
			catch (WXException ex)
			{
				logger.LogError(ex.InnerException ?? ex, ex.Message);
			}
			catch (Exception ex)
			{
				logger.LogError(ex.InnerException ?? ex, ex.Message);
			}
			return string.Empty;
		}

		/// <summary>
		/// 获取js令牌
		/// </summary>
		/// <param name="appID"></param>
		/// <param name="appSecret"></param>
		/// <returns></returns>
		public string GetJSTicket(string appID, string appSecret)
		{
			try
			{
				if (string.IsNullOrEmpty(appID))
				{
					throw new WXException("appid不能为空");
				}

				string accessToken = BaseApi.GetAccessToken(appID, appSecret);
				if (string.IsNullOrEmpty(accessToken))
				{
					throw new WXException("未获取到相关的ACCESS_TOKEN");
				}

				return GetJSTicket(accessToken);
			}
			catch (WXException ex)
			{
				logger.LogError(ex.InnerException ?? ex, ex.Message);
			}
			catch (Exception ex)
			{
				logger.LogError(ex.InnerException ?? ex, ex.Message);
			}
			return string.Empty;
		}

		/// <summary>
		/// 获取js令牌
		/// </summary>
		/// <param name="wxConfig"></param>
		/// <returns></returns>
		public string GetJSTicket(WX_AppConfig wxConfig)
		{
			try
			{
				if (wxConfig == null)
				{
					throw new WXException("app配置信息不能为空");
				}

				string accessToken = BaseApi.GetAccessToken(wxConfig.AppID, wxConfig.AppSecret);
				if (string.IsNullOrEmpty(accessToken))
				{
					throw new WXException("未获取到相关的ACCESS_TOKEN");
				}

				return GetJSTicket(accessToken);
			}
			catch (WXException ex)
			{
				logger.LogError(ex.InnerException ?? ex, ex.Message);
			}
			catch (Exception ex)
			{
				logger.LogError(ex.InnerException ?? ex, ex.Message);
			}
			return string.Empty;
		}

		/// <summary>
		/// 生成签名（字段名和字段值都采用原始值，不进行URL 转义）
		/// </summary>
		/// <param name="noncestr">随机字符串</param>
		/// <param name="jsApiTicket"></param>
		/// <param name="timestamp">时间戳</param>
		/// <param name="url">当前调用JS接口页面的完整URL，不包含#及其后面部分</param>
		/// <returns></returns>
		public string Signature(string noncestr, string jsApiTicket, string timestamp, string url)
		{
			try
			{
				if (string.IsNullOrEmpty(noncestr))
				{
					throw new WXException("生成签名失败，参数noncestr不能为空");
				}
				if (string.IsNullOrEmpty(jsApiTicket))
				{
					throw new WXException("生成签名失败，参数jsapi_ticket不能为空");
				}
				if (string.IsNullOrEmpty(timestamp))
				{
					throw new WXException("生成签名失败，参数timestamp不能为空");
				}
				if (string.IsNullOrEmpty(url))
				{
					throw new WXException("生成签名失败，参数url不能为空");
				}
				if (!url.StartsWith("http://") && !url.StartsWith("https://"))
				{
					url = HttpContextHelper.Current.Request.Scheme + "://" + url;
				}
				if (logger.IsEnabled(LogLevel.Debug))
				{
					logger.LogDebug($"签名参数：noncestr:{noncestr},jsApiTicket:{jsApiTicket},timestamp:{timestamp},url:{url}");
				}
				SortedDictionary<string, string> paras = new SortedDictionary<string, string>();
				paras.Add("noncestr", noncestr);
				paras.Add("jsapi_ticket", jsApiTicket);
				paras.Add("timestamp", timestamp);
				paras.Add("url", url);
				string str = string.Empty;
				foreach (KeyValuePair<string, string> p in paras)
				{
					str += p.Key + "=" + p.Value + "&";
				}
				str = str.TrimEnd('&');
				string signature = EncryptHelper.SHA1Hash(str);//对所有待签名参数按照字段名的ASCII 码从小到大排序（字典序）后，使用URL键值对的格式（即key1=value1&key2=value2…）拼接成字符串
				return signature;
			}
			catch (WXException ex)
			{
				logger.LogError(ex.InnerException ?? ex, ex.Message);
			}
			catch (Exception ex)
			{
				logger.LogError(ex.InnerException ?? ex, ex.Message);
			}
			return string.Empty;
		}
	}
}
